const Wallet = require('ethereumjs-wallet');
const Mnemonic = require('bitcore-mnemonic');
const Bitcore = require('bitcore-lib');
const Random = Bitcore.crypto.Random;
const Hash = Bitcore.crypto.Hash;
const Scrypt = require('scrypt-async');
const Nacl = require('tweetnacl');
const seedType = "m/44'/60'/0'/0";
const Web3 = require('web3');
const BigNumber = require('bignumber.js');
const abi = require('../util/abi');
const Tx = require('ethereumjs-tx');

//const seedType= "m/0'/0'/0'";

class WalletUtil {

    constructor() {
        let host = 'https://rinkeby.infura.io/NFxh6F3WBNGnQdTG0iVf';
        this.web3 = new Web3(new Web3.providers.HttpProvider(host));
    }


    /**
     *创建钱包 同时生成种子，私钥，地址 ，keyStore
     */
    createWallet(pwd, callback) {
        let res = {
            code: 0,
            data: '',
            msg: ''
        };
        let salt = Random.getRandomBuffer(32).toString('base64');
        this.getKeyFromPasswordAndSalt(pwd, salt, key => {
            try {
                let seed = this.generateRandomSeed(pwd);
                seed = seed.toString();
                let privateKeyBuffer = this.proccessSeed(seed, key);
                let privateKey = privateKeyBuffer.toString('hex');
                let wallet = Wallet.fromPrivateKey(privateKeyBuffer);
                let address = wallet.getAddressString();
                let keystore = wallet.toV3(pwd, {
                    kdf: "scrypt",
                    n: 8192
                });
                let v3Filename = wallet.getV3Filename();
                if (callback) {
                    res.data = {
                        seed: seed,
                        privateKey: privateKey,
                        address: address,
                        keystore: keystore,
                        keystoreName: v3Filename,
                        keystorePwd: pwd
                    };
                    callback(res);
                }
            } catch (err) {
                res.code = -1;
                res.msg = err.message;
                if (callback) {
                    callback(res);
                }
            }
        });
    }

    /**
     * 通过私钥生成钱包对象
     * @param privateKey
     * @param pwd
     */
    importWalletByPrivateKey(privateKey, pwd, callback) {
        let res = {
            code: 0,
            data: '',
            msg: ''
        };
        try {
            let wallet = Wallet.fromPrivateKey(new Buffer(privateKey, 'hex'));
            let address = wallet.getAddressString();
            let keystore = wallet.toV3(pwd, {
                kdf: "scrypt",
                n: 8192
            });
            let v3Filename = wallet.getV3Filename();

            if (callback) {
                res.data = {
                    privateKey: privateKey,
                    address: address,
                    keystore: keystore,
                    keystoreName: v3Filename,
                    keystorePwd: pwd
                };
                callback(res);
            }
        } catch (err) {
            res.code = -1;
            res.msg = err.message;
            if (callback) {
                callback(res);
            }
        }
    }


    /**
     * 导入种子生成钱包
     * @param seed
     * @param pwd
     */
    importWalletBySeed(seed, pwd, callback) {
        let res = {
            code: 0,
            data: '',
            msg: ''
        };
        let salt = Random.getRandomBuffer(32).toString('base64');
        this.getKeyFromPasswordAndSalt(pwd, salt, key => {
            try {
                let privateKeyBuffer = this.proccessSeed(seed, key);
                let privateKey = privateKeyBuffer.toString('hex');
                let wallet = Wallet.fromPrivateKey(privateKeyBuffer);
                let address = wallet.getAddressString();
                let keystore = wallet.toV3(pwd, {
                    kdf: "scrypt",
                    n: 8192
                });
                let v3Filename = wallet.getV3Filename();
                if (callback) {
                    res.data = {
                        seed: seed,
                        privateKey: privateKey,
                        address: address,
                        keystore: keystore,
                        keystoreName: v3Filename,
                        keystorePwd: pwd
                    };
                    callback(res);
                }
            } catch (err) {
                res.code = -1;
                res.msg = err.message;
                if (callback) {
                    callback(res);
                }
            }
        });
    }

    /**
     * 种子生成私钥的方法
     * @param seed
     * @param key
     */
    proccessSeed(seed, key) {
        let ui8arr = new Uint8Array(key);
        let hdRoot1 = new Mnemonic(seed).toHDPrivateKey().xprivkey;
        let hdRootKey = new Bitcore.HDPrivateKey(hdRoot1);
        let hdPathKey = hdRootKey.derive(seedType).xprivkey;
        let encHdRootPriv = this.encryptString(hdPathKey, ui8arr);
        let hdRoot2 = this.decryptString(encHdRootPriv, ui8arr);
        let hdprivkey = new Bitcore.HDPrivateKey(hdRoot2).derive(0);
        let privateKeyBuffer = hdprivkey.privateKey.toBuffer();
        return privateKeyBuffer;
    }

    /**
     * 导入keyStore生成钱包
     * @param keystore
     * @param pwd
     */
    importWalletByKeyStore(keystore, pwd, callback) {
        let res = {
            code: 0,
            data: '',
            msg: ''
        };
        try {
            let wallet = Wallet.fromV3(keystore, pwd);
            let privateKey = wallet.getPrivateKeyString().substring(2);
            let address = wallet.getAddressString();
            if (callback) {
                res.data = {
                    privateKey: privateKey,
                    address: address,
                    keystore: JSON.parse(keystore),
                    keystorePwd: pwd
                };
                callback(res);
            }
        } catch (err) {
            res.code = -1;
            res.msg = err.message;
            if (callback) {
                callback(res);
            }
        }

    }


    /**
     * 生成种子
     * @param pwd
     */
    generateRandomSeed(pwd) {
        let entBuf = new Buffer(pwd);
        let randBuf = Random.getRandomBuffer(256 / 8);
        let totalEnt = Buffer.concat([randBuf, entBuf]);
        let hashedEnt = Hash.sha256(totalEnt).slice(0, 128 / 8);
        return new Mnemonic(hashedEnt, Mnemonic.Words.ENGLISH);
    }

    /**
     * 给密码加盐
     * @param pwd
     * @param salt
     */
    getKeyFromPasswordAndSalt(pwd, salt, callback) {
        let logN = 14;
        let r = 8;
        let dkLen = 32;
        let interruptStep = 200;
        Scrypt(pwd, salt, logN, r, dkLen, interruptStep, callback, null);
    }

    /**
     * 加密字符串
     * @param string
     * @param ui8arr
     */
    encryptString(string, ui8arr) {
        let nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
        let encObj = Nacl.secretbox(Nacl.util.decodeUTF8(string), nonce, ui8arr);
        let encString = {
            'encStr': Nacl.util.encodeBase64(encObj),
            'nonce': Nacl.util.encodeBase64(nonce)
        };
        return encString;
    }

    /**
     * 解密字符串
     * @param string
     * @param ui8arr
     */
    decryptString(encryptedStr, ui8arr) {
        let secretbox = Nacl.util.decodeBase64(encryptedStr.encStr);
        let nonce = Nacl.util.decodeBase64(encryptedStr.nonce);
        let decryptedStr = Nacl.secretbox.open(secretbox, nonce, ui8arr);
        return Nacl.util.encodeUTF8(decryptedStr);
    }

    /**
     * 获取以太币余额
     */
    getEthBalance(fromAddress) {
        return new Promise((resolve, reject) => {
            let info = {
                code: 0,
                data: '',
                msg: ''
            };
            this.web3.eth.getBalance(fromAddress, (err, res) => {
                if (!err) {
                    if (res) {
                        let balance = this.web3.utils.fromWei(res);
                        info.data = {balance: balance};
                    } else {
                        info.code = -1;
                        info.msg = "获取余额失败";
                    }
                } else {
                    info.code = -1;
                    info.msg = err;
                }
                if (resolve) {
                    resolve(info);
                }
            });
        });
    }

    /**
     * 查询代币信息
     */
    getTokenBalance(fromAddress, tokenAddress) {
        return new Promise((resolve, reject) => {
            let info = {
                code: 0,
                data: '',
                msg: ''
            };
            let tokenName;
            let tokenSymbol;
            let decimals;
            let balance;
            let contract = new this.web3.eth.Contract(abi, tokenAddress);
            return contract.methods.name().call()
                .then((name) => {
                    tokenName = name;
                    return contract.methods.symbol().call();
                })
                .then((symbol) => {
                    tokenSymbol = symbol;
                    return contract.methods.decimals().call();
                })
                .then((dec) => {
                    decimals = dec;
                    return contract.methods.balanceOf(fromAddress).call();
                })
                .then((balance) => {
                    balance = new BigNumber(balance);
                    balance = balance.dividedBy(new BigNumber('1e' + decimals));
                    if (resolve) {
                        info.data = {
                            fromAddress: fromAddress,
                            tokenAddress: tokenAddress,
                            tokenName: tokenName,
                            decimals: decimals,
                            tokenSymbol: tokenSymbol,
                            balance: balance.toString()
                        };
                        resolve(info);
                    }
                });
        });
    }

    /**
     * 以太币转账
     */
    sendEthTransaction(fromAddress, priKey, toAddress, amount) {
        return new Promise((resolve, reject) => {
            let info = {
                code: 0,
                data: '',
                msg: ''
            };
            let utils = this.web3.utils;
            let txOptions = {
                from: fromAddress,
                gasPrice: utils.toHex(utils.toWei('5', 'gwei')),
                to: toAddress,
                value: utils.toHex(utils.toWei(amount, 'ether')),
                data: ""
            };
            return this.web3.eth.getTransactionCount(fromAddress)
                .then(nonce => {
                    txOptions.nonce = utils.toHex(nonce);
                    return this.web3.eth.estimateGas(txOptions);
                })
                .then(gasLimit => {
                    gasLimit = parseInt(gasLimit) + 1;
                    txOptions.gasLimit = utils.toHex(gasLimit);
                    let tx = new Tx(txOptions);
                    tx.sign(new Buffer(priKey, 'hex'));
                    let serializedTx = tx.serialize().toString('hex');
                    this.web3.eth.sendSignedTransaction('0x' + serializedTx)
                        .on('transactionHash', hash => {
                            if (resolve) {
                                txOptions.hash = hash;
                                txOptions.amount = amount;
                                info.data = txOptions;
                                resolve(info);
                            }
                        })
                        .catch(err => {
                            info.code = -1;
                            info.msg = err.message;
                            if (resolve) {
                                resolve(info);
                            }
                        });
                }).catch(err => {
                    info.code = -1;
                    info.msg = err.message;
                    if (resolve) {
                        resolve(info);
                    }
                })

        });
    }


    /**
     * 代币转账
     */
    sendTokenTransaction(fromAddress, priKey, toAddress, tokenAddr, amount) {
        return new Promise((resolve, reject) => {
            let info = {
                code: 0,
                data: '',
                msg: ''
            };
            let contract = new this.web3.eth.Contract(abi, tokenAddr);
            let utils = this.web3.utils;
            let txOptions = {
                gasPrice: utils.toHex(utils.toWei('5', 'gwei')),
                to: tokenAddr,
                value: '0x00',
            };
            return contract.methods.decimals().call()
                .then((decimals) => {
                    let amount1 = new BigNumber(amount);
                    amount1 = amount1.multipliedBy(new BigNumber('1e' + decimals)).toString(10);
                    txOptions.data = contract.methods.transfer(toAddress, amount1).encodeABI();
                    return contract.methods.transfer(toAddress, amount1).estimateGas({from: fromAddress});
                })
                .then(gasLimit => {
                    txOptions.gasLimit = utils.toHex(parseInt(gasLimit) + 1);
                    return this.web3.eth.getTransactionCount(fromAddress);
                })
                .then(nonce => {
                    txOptions.nonce = utils.toHex(nonce);
                    let tx = new Tx(txOptions);
                    tx.sign(new Buffer(priKey, 'hex'));
                    let serializedTx = tx.serialize().toString('hex');
                    this.web3.eth.sendSignedTransaction('0x' + serializedTx)
                        .on('transactionHash', hash => {
                            if (resolve) {
                                txOptions.from=fromAddress;
                                txOptions.hash = hash;
                                txOptions.amount = amount;
                                txOptions.tokenAddr = tokenAddr;
                                info.data = txOptions;
                                resolve(info);
                            }
                        })
                        .catch(err => {
                            info.code = -1;
                            info.msg = err.message;
                            if (resolve) {
                                resolve(info);
                            }
                        });
                })
                .catch(err => {
                    info.code = -1;
                    info.msg = err.message;
                    if (resolve) {
                        resolve(info);
                    }
                });
        });
    }
}

module.exports = WalletUtil;